Domine a API User Timing para criar métricas de desempenho personalizadas e significativas. Vá além das métricas web vitais padrão para identificar gargalos e otimizar a experiência do usuário.
Dominando o Desempenho Frontend: Um Mergulho Profundo na API User Timing
No cenário digital moderno, o desempenho do frontend não é um luxo; é um requisito fundamental para o sucesso. Para um público global, um site lento e que não responde pode levar à frustração do usuário, diminuição do engajamento e um impacto negativo direto nos resultados de negócios. Temos excelentes métricas padronizadas como as Core Web Vitals (Largest Contentful Paint, First Input Delay, Cumulative Layout Shift) que nos dão uma compreensão básica da experiência do usuário. No entanto, essas métricas, embora cruciais, contam apenas parte da história.
E quanto ao desempenho de funcionalidades específicas da aplicação? Quanto tempo leva para os resultados da busca aparecerem depois que um usuário digita uma consulta? Quanto tempo seu componente complexo de visualização de dados leva para renderizar após receber dados de uma API? Como uma nova funcionalidade impacta a velocidade das transições de rota da sua aplicação de página única (SPA)? As métricas padrão não podem responder a essas perguntas granulares e críticas para o negócio. É aqui que a API User Timing entra em cena, capacitando os desenvolvedores a criar medições de desempenho personalizadas e de alta precisão, adaptadas às suas aplicações únicas.
Este guia abrangente irá orientá-lo em tudo o que você precisa saber para aproveitar a API User Timing, desde os conceitos básicos de marcas e medidas até técnicas avançadas usando o PerformanceObserver. Ao final, você estará equipado para ir além das métricas genéricas и começar a contar a história de desempenho única da sua aplicação.
O que é a API de Desempenho? Um Contexto Mais Amplo
Antes de mergulharmos fundo na User Timing, é importante entender que ela faz parte de um conjunto maior de ferramentas coletivamente conhecidas como a API de Desempenho. Esta API do navegador fornece acesso a dados de temporização de alta precisão relacionados à navegação, carregamento de recursos e muito mais. O objeto global `window.performance` é o seu ponto de entrada para este poderoso conjunto de ferramentas.
A API de Desempenho é composta por várias interfaces, incluindo:
- Navigation Timing (Temporização de Navegação): Fornece informações detalhadas de temporização sobre o processo de navegação do documento, como o tempo gasto em pesquisas de DNS, handshakes TCP e recebimento do primeiro byte.
- Resource Timing (Temporização de Recursos): Oferece dados detalhados de temporização de rede para cada recurso carregado pela página, incluindo imagens, scripts e arquivos CSS.
- Paint Timing (Temporização de Pintura): Expõe os tempos para First Paint e First Contentful Paint.
- User Timing (Temporização do Usuário): O foco do nosso artigo, que permite aos desenvolvedores criar seus próprios timestamps personalizados (marcas) e medir a duração entre eles (medidas).
Essas APIs trabalham juntas para fornecer uma visão holística do desempenho da sua aplicação. Nosso objetivo hoje é dominar a parte de User Timing, que nos dá o poder de adicionar nossos próprios pontos de verificação personalizados a esta linha do tempo de desempenho.
Os Conceitos Centrais: Marcas e Medidas
A API User Timing é enganosamente simples, girando em torno de dois conceitos fundamentais: marcas e medidas. Pense nisso como usar um cronômetro. Você pressiona um botão para marcar um tempo de início e o pressiona novamente para marcar um tempo de fim. A duração entre essas duas pressões é a sua medição.
Criando Marcas de Desempenho: `performance.mark()`
Uma 'marca' (mark) é um timestamp nomeado de alta resolução registrado em um ponto específico na execução da sua aplicação. É como plantar uma bandeira na sua linha do tempo de desempenho. Você pode criar quantas marcas precisar para identificar momentos-chave em uma jornada do usuário ou ciclo de vida de um componente.
A sintaxe é direta:
performance.mark(markName, [markOptions]);
markName: Uma string que representa o nome único para sua marca. Escolha nomes descritivos!markOptions(opcional): Um objeto que pode conter uma propriedadedetailpara anexar metadados extras, e umstartTimepara especificar um timestamp personalizado.
Exemplo Básico: Marcando um Evento
Digamos que queremos marcar o início de uma chamada de função importante.
function processLargeDataset() {
// Coloca uma bandeira logo antes do trabalho pesado começar
performance.mark('processLargeDataset:start');
// ... lógica computacional pesada ...
console.log('Processamento do conjunto de dados completo.');
// Coloca outra bandeira quando terminar
performance.mark('processLargeDataset:end');
}
processLargeDataset();
Neste exemplo, criamos dois timestamps na linha do tempo de desempenho do navegador: `processLargeDataset:start` e `processLargeDataset:end`. No momento, eles são apenas pontos no tempo. Seu verdadeiro poder é desbloqueado quando os usamos para criar uma medida.
Adicionando Contexto com a Propriedade `detail`
Às vezes, apenas um timestamp não é suficiente. Você pode querer incluir contexto extra sobre o que estava acontecendo naquele momento. A propriedade `detail` é perfeita para isso. Ela pode conter quaisquer dados que possam ser clonados estruturalmente (como objetos, arrays, strings, números).
Imagine que estamos marcando o início da renderização de um componente e queremos saber quantos itens ele estava renderizando.
function renderProductList(products) {
const itemCount = products.length;
performance.mark('ProductList:render:start', {
detail: {
itemCount: itemCount,
source: 'initial-load'
}
});
// ... lógica de renderização do componente ...
performance.mark('ProductList:render:end');
}
const sampleProducts = new Array(1000).fill(0);
renderProductList(sampleProducts);
Este contexto adicional é inestimável ao analisar dados de desempenho posteriormente. Você poderia, por exemplo, correlacionar os tempos de renderização com o número de itens para ver se há uma relação linear ou exponencial.
Criando Medidas de Desempenho: `performance.measure()`
Uma 'medida' (measure) captura a duração entre dois pontos no tempo. É o cálculo que diz "quanto tempo" algo levou. Mais comumente, você medirá o tempo entre duas de suas marcas personalizadas.
A sintaxe tem algumas variações:
performance.measure(measureName, startMarkOrOptions, [endMark]);
measureName: Uma string que representa o nome único para sua medição.startMarkOrOptions(opcional): Uma string com o nome da marca inicial. Também pode ser um objeto de opções com `start`, `end`, `duration` e `detail`.endMark(opcional): Uma string com o nome da marca final.
Exemplo Básico: Medindo a Duração de uma Função
Vamos expandir nosso exemplo `processLargeDataset` e realmente medir quanto tempo levou.
function processLargeDataset() {
performance.mark('processLargeDataset:start');
// ... lógica computacional pesada ...
performance.mark('processLargeDataset:end');
// Agora, crie a medida
performance.measure(
'processLargeDataset:duration',
'processLargeDataset:start',
'processLargeDataset:end'
);
}
processLargeDataset();
Depois que este código é executado, o buffer de desempenho do navegador conterá uma nova entrada chamada `processLargeDataset:duration`. Esta entrada terá uma propriedade `duration` contendo o tempo preciso, em milissegundos, que decorreu entre as marcas de início e fim.
Cenários de Medição Avançados
O método `measure()` é muito flexível. Você nem sempre precisa fornecer duas marcas.
- Do Início da Navegação até uma Marca: Você pode medir o tempo desde o início da navegação da página até uma de suas marcas personalizadas. Isso é incrivelmente útil para medir coisas como "Tempo para Componente Interativo".
// Mede desde o início da navegação até que o componente principal esteja pronto performance.measure('timeToInteractiveHeader', 'navigationStart', 'headerComponent:ready'); - De uma Marca até Agora: Se você omitir o `endMark`, a medida será calculada da sua `startMark` até o momento atual.
// Mede desde a marca inicial até que esta linha de código seja executada performance.measure('timeSinceDataRequest', 'api:fetch:start'); - Usando o Objeto de Opções: Você também pode passar um objeto de configuração para definir a medida, o que é útil para adicionar uma propriedade `detail`.
performance.measure('complexRender:duration', { start: 'complexRender:start', end: 'complexRender:end', detail: { renderType: 'canvas' } });
Acessando e Limpando Entradas de Desempenho
Criar marcas e medidas é apenas metade da batalha. Você precisa de uma maneira de recuperar esses dados para analisá-los. O objeto `performance` fornece vários métodos para isso.
performance.getEntries(): Retorna um array de todas as entradas de desempenho no buffer (incluindo temporizações de recursos, de navegação, etc.).performance.getEntriesByType(type): Retorna um array de entradas de um tipo específico. Você usará com mais frequência `performance.getEntriesByType('mark')` e `performance.getEntriesByType('measure')`.performance.getEntriesByName(name, [type]): Retorna um array de entradas com um nome específico (e, opcionalmente, um tipo específico).
Exemplo: Registrando Medidas no Console
// Depois de executar nossos exemplos anteriores...
const allMeasures = performance.getEntriesByType('measure');
console.log(allMeasures);
// Um objeto de entrada de medida se parece com algo assim:
// {
// "name": "processLargeDataset:duration",
// "entryType": "measure",
// "startTime": 12345.67,
// "duration": 150.89
// }
const specificMeasure = performance.getEntriesByName('processLargeDataset:duration');
console.log(`O processamento levou: ${specificMeasure[0].duration}ms`);
Importante: Limpando o Buffer de Desempenho
O buffer de desempenho do navegador não é infinito. Para evitar vazamentos de memória e manter suas medições relevantes, é uma boa prática limpar as marcas e medidas que você criou assim que terminar de usá-las.
performance.clearMarks([name]): Limpa todas as marcas, ou apenas as marcas com o nome especificado.performance.clearMeasures([name]): Limpa todas as medidas, ou apenas as medidas com o nome especificado.
Um padrão comum é recuperar os dados, processá-los ou enviá-los e, em seguida, limpá-los.
function analyzeAndClear() {
const myMeasures = performance.getEntriesByName('processLargeDataset:duration');
// Envia myMeasures para um serviço de análise...
sendToAnalytics(myMeasures);
// Limpa para liberar memória
performance.clearMarks('processLargeDataset:start');
performance.clearMarks('processLargeDataset:end');
performance.clearMeasures('processLargeDataset:duration');
}
Casos de Uso Práticos do Mundo Real para a User Timing
Agora que entendemos a mecânica, vamos explorar como aplicar a API User Timing para resolver desafios de desempenho do mundo real. Estes exemplos são agnósticos de framework e podem ser adaptados a qualquer stack de frontend.
1. Medindo Durações de Chamadas de API
Entender quanto tempo sua aplicação espera por dados é crítico. Você pode facilmente envolver sua lógica de busca de dados com marcas e medidas.
async function fetchUserData(userId) {
const markStart = `api:getUser:${userId}:start`;
const markEnd = `api:getUser:${userId}:end`;
const measureName = `api:getUser:${userId}:duration`;
performance.mark(markStart);
try {
const response = await fetch(`https://api.example.com/users/${userId}`);
if (!response.ok) {
throw new Error('A resposta da rede não foi ok');
}
return await response.json();
} catch (error) {
console.error('Erro no fetch:', error);
// Você pode até adicionar detalhes sobre erros!
performance.mark(markEnd, { detail: { status: 'error', message: error.message } });
} finally {
// Garante que a marca final e a medida sejam sempre criadas
if (performance.getEntriesByName(markEnd).length === 0) {
performance.mark(markEnd, { detail: { status: 'success' } });
}
performance.measure(measureName, markStart, markEnd);
}
}
fetchUserData('123');
Este padrão fornece temporizações precisas para cada chamada de API, permitindo que você identifique endpoints lentos diretamente a partir de dados de usuários reais.
2. Rastreando Tempos de Renderização de Componentes em SPAs
Para frameworks como React, Vue ou Angular, medir o tempo que um componente leva para montar e renderizar é um caso de uso principal. Isso ajuda a identificar componentes complexos que podem estar retardando sua aplicação.
Exemplo com React Hooks:
import React, { useLayoutEffect, useEffect, useRef } from 'react';
function MyHeavyComponent({ data }) {
const componentId = useRef(`MyHeavyComponent-${Math.random()}`).current;
const markStartName = `${componentId}:render:start`;
const markEndName = `${componentId}:render:end`;
const measureName = `${componentId}:render:duration`;
// useLayoutEffect é executado sincronicamente após todas as mutações do DOM.
// É o lugar perfeito para marcar o início da medição de renderização.
useLayoutEffect(() => {
performance.mark(markStartName);
}, []); // Executa apenas na montagem inicial
// useEffect é executado assincronamente após a renderização ser confirmada na tela.
// Este é um bom lugar para marcar o fim.
useEffect(() => {
performance.mark(markEndName);
performance.measure(measureName, markStartName, markEndName);
// Registra o resultado para demonstração
const measure = performance.getEntriesByName(measureName)[0];
if (measure) {
console.log(`${measureName} levou ${measure.duration}ms`);
}
// Limpeza
performance.clearMarks(markStartName);
performance.clearMarks(markEndName);
performance.clearMeasures(measureName);
}, []); // Executa apenas na montagem inicial
return (
// ... JSX para o componente pesado ...
);
}
3. Quantificando Jornadas Críticas do Usuário
O uso mais impactante da User Timing é medir interações do usuário de múltiplos passos que são críticas para o seu negócio. Isso transcende métricas técnicas simples e mede a velocidade percebida da funcionalidade principal da sua aplicação.
Considere um processo de checkout de e-commerce:
const checkoutButton = document.getElementById('checkout-btn');
checkoutButton.addEventListener('click', () => {
// 1. O usuário clica no botão 'checkout'
performance.mark('checkout:journey:start');
// ... código para validar o carrinho, navegar para a página de pagamento, etc. ...
});
// Na página de pagamento, após o formulário de pagamento ser renderizado e interativo
function onPaymentFormReady() {
performance.mark('checkout:paymentForm:ready');
performance.measure('checkout:timeToPaymentForm', 'checkout:journey:start', 'checkout:paymentForm:ready');
}
// Após o pagamento ser processado com sucesso e a tela de confirmação ser mostrada
function onPaymentSuccess() {
performance.mark('checkout:journey:end');
performance.measure('checkout:totalJourney:duration', 'checkout:journey:start', 'checkout:journey:end');
// Agora você tem duas métricas poderosas para analisar e otimizar.
}
4. Teste A/B de Melhorias de Desempenho
Quando você refatora um pedaço de código ou introduz um novo algoritmo, como você prova que ele é realmente mais rápido para usuários reais? A User Timing fornece dados objetivos para testes A/B.
Imagine que você tem dois algoritmos de ordenação diferentes que deseja testar:
function sortProducts(products, algorithmVersion) {
const markStart = `sort:v${algorithmVersion}:start`;
const markEnd = `sort:v${algorithmVersion}:end`;
const measureName = `sort:v${algorithmVersion}:duration`;
performance.mark(markStart);
if (algorithmVersion === 'A') {
// ... executa o algoritmo de ordenação antigo ...
} else {
// ... executa o novo algoritmo de ordenação otimizado ...
}
performance.mark(markEnd);
performance.measure(measureName, markStart, markEnd);
}
// Com base em uma flag de teste A/B, você chamaria um ou outro.
// Mais tarde, em suas análises, você pode comparar a duração média de
// 'sort:vA:duration' vs 'sort:vB:duration' para ver qual foi mais rápido.
Visualizando e Analisando Suas Métricas Personalizadas
Criar métricas personalizadas é inútil se você не analisar os dados. Existem duas maneiras principais de abordar isso: localmente durante o desenvolvimento e de forma agregada em produção.
Usando as Ferramentas de Desenvolvedor do Navegador
Navegadores modernos como Chrome e Firefox têm excelente suporte para visualizar marcas e medidas da User Timing em suas ferramentas de perfil de desempenho.
- Abra as Ferramentas de Desenvolvedor do seu navegador (F12 ou Ctrl+Shift+I).
- Vá para a aba Performance.
- Comece a gravar um perfil e, em seguida, execute as ações em sua aplicação que acionam suas marcas e medidas personalizadas.
- Pare de gravar.
Na visualização da linha do tempo, você encontrará uma linha dedicada chamada Timings. Suas marcas personalizadas aparecerão como linhas verticais, e suas medidas serão exibidas como barras coloridas mostrando sua duração. Passar o mouse sobre elas revelará seus nomes e tempos exatos. Esta é uma maneira incrivelmente poderosa de depurar problemas de desempenho durante o desenvolvimento.
Enviando Dados para Serviços de Analytics e RUM
Para monitoramento em produção, você precisa coletar esses dados de seus usuários e enviá-los para um local central para agregação e análise. Esta é uma parte central do Real User Monitoring (RUM).
O fluxo de trabalho geral é:
- Coletar as medidas de desempenho que lhe interessam.
- Formatá-las em uma carga útil adequada (por exemplo, JSON).
- Enviar a carga útil para um endpoint de análise. Este pode ser um serviço de terceiros como Datadog, New Relic, Sentry, ou até mesmo o Google Analytics (via eventos personalizados), ou um backend personalizado que você controla.
function sendPerformanceData() {
// Nós só nos importamos com as nossas medidas de aplicação personalizadas
const appMeasures = performance.getEntriesByType('measure').filter(
(entry) => entry.name.startsWith('app:') // Use uma convenção de nomenclatura!
);
if (appMeasures.length > 0) {
const payload = JSON.stringify(appMeasures.map(measure => ({
name: measure.name,
duration: measure.duration,
startTime: measure.startTime,
details: measure.detail, // Envie nosso contexto rico
path: window.location.pathname // Adicione mais contexto
})));
// Use navigator.sendBeacon para envio de dados confiável e não bloqueante
navigator.sendBeacon('https://analytics.example.com/performance', payload);
// Limpa as medidas que foram enviadas
appMeasures.forEach(measure => {
performance.clearMeasures(measure.name);
// Também limpa as marcas associadas
});
}
}
// Chame esta função em um momento apropriado, por exemplo, quando a página está prestes a ser descarregada
window.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden') {
sendPerformanceData();
}
});
Técnicas Avançadas e Melhores Práticas
Para dominar verdadeiramente a API User Timing, vamos olhar para algumas funcionalidades avançadas e melhores práticas que tornarão sua instrumentação mais robusta e eficiente.
Usando `PerformanceObserver` para Monitoramento Assíncrono
Os métodos `getEntries*()` exigem que você verifique manualmente o buffer de desempenho. Isso tem duas desvantagens: você pode executar sua verificação tarde demais e perder entradas se o buffer encher e for limpo, e a própria verificação pode ter um custo de desempenho menor. A solução moderna e preferida é o `PerformanceObserver`.
Um `PerformanceObserver` permite que você se inscreva em eventos de entrada de desempenho. Sua função de callback será invocada assincronamente sempre que novas entradas dos tipos que você está observando forem registradas.
// 1. Crie uma função de callback para lidar com novas entradas
const observerCallback = (list) => {
for (const entry of list.getEntries()) {
console.log('Nova medida observada:', entry.name, entry.duration);
// Aqui você pode enviar imediatamente a entrada para seu serviço de análise
// sem precisar verificar ou esperar.
}
};
// 2. Crie a instância do observador
const observer = new PerformanceObserver(observerCallback);
// 3. Comece a observar os tipos de entrada 'mark' e 'measure'
// A opção 'buffered: true' garante que você obtenha entradas que foram criadas
// *antes* do observador ser registrado.
observer.observe({ entryTypes: ['mark', 'measure'], buffered: true });
// Agora, sempre que performance.mark() ou performance.measure() for chamado em qualquer lugar
// em sua aplicação, o observerCallback será acionado com a nova entrada.
// Para parar de observar mais tarde:
// observer.disconnect();
Usar o `PerformanceObserver` é mais eficiente, mais confiável e deve ser sua escolha padrão para coletar dados de desempenho em um ambiente de produção.
Estabeleça uma Convenção de Nomenclatura Clara
À medida que sua aplicação cresce, você acumulará muitas métricas personalizadas. Sem uma convenção de nomenclatura consistente, seus dados se tornarão difíceis de filtrar e analisar. Adote um padrão que forneça contexto.
Uma boa convenção poderia ser: [appName]:[featureOrComponent]:[eventName]:[status]
ecom:ProductGallery:render:startecom:ProductGallery:render:endecom:ProductGallery:render:durationadmin:DataTable:fetchApi:startadmin:DataTable:fetchApi:duration
Esta estrutura torna trivial filtrar todas as métricas relacionadas à `ProductGallery` ou encontrar todas as durações de `fetchApi` em toda a aplicação.
Abstraia em um Serviço Utilitário
Para garantir consistência e reduzir o código repetitivo, envolva as chamadas de `performance` em seu próprio módulo ou serviço utilitário. Isso também facilita a ativação ou desativação do monitoramento de desempenho com base no ambiente.
// performance-service.js
const IS_PERFORMANCE_MONITORING_ENABLED = process.env.NODE_ENV === 'production' || window.location.search.includes('perf=true');
export const perfMark = (name, options) => {
if (!IS_PERFORMANCE_MONITORING_ENABLED) return;
performance.mark(name, options);
};
export const perfMeasure = (name, start, end) => {
if (!IS_PERFORMANCE_MONITORING_ENABLED) return;
performance.measure(name, start, end);
};
export const startJourney = (name) => {
perfMark(`${name}:start`);
};
export const endJourney = (name) => {
const startMark = `${name}:start`;
const endMark = `${name}:end`;
const measureName = `${name}:duration`;
perfMark(endMark);
perfMeasure(measureName, startMark, endMark);
// Opcionalmente, limpe as marcas aqui
};
// No seu componente:
// import { startJourney, endJourney } from './performance-service';
// startJourney('ecom:checkout');
// ...mais tarde...
// endJourney('ecom:checkout');
Conclusão: Assumindo o Controle da História de Desempenho da Sua Aplicação
Enquanto métricas padrão como as Core Web Vitals fornecem uma verificação essencial da saúde do seu site, elas não iluminam o desempenho das funcionalidades e interações que tornam sua aplicação única. A API User Timing é a ponte que fecha essa lacuna. Ela fornece um mecanismo simples, mas profundamente poderoso, para medir o que realmente importa para seus usuários e para o seu negócio.
Ao implementar marcas e medidas personalizadas, você transforma a otimização de desempenho de um jogo de adivinhação em uma ciência orientada por dados. Você pode identificar as funções, componentes ou fluxos de usuário exatos que estão causando gargalos, validar o impacto de seus esforços de refatoração com números objetivos e, finalmente, construir uma experiência mais rápida e agradável para seu público global.
Comece pequeno. Identifique a jornada do usuário mais crítica em sua aplicação—seja a busca por um produto, o envio de um formulário ou o carregamento de um painel de dados. Instrumente-a com `performance.mark()` e `performance.measure()`. Analise os resultados em suas ferramentas de desenvolvedor. Uma vez que você veja a clareza que isso proporciona, você estará capacitado para contar a história completa de desempenho da sua aplicação, uma métrica personalizada de cada vez.